package api

import (
	"bytes"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"text/tabwriter"

	"github.com/mmcloughlin/avo/internal/inst"
)

// Function represents a function that constructs some collection of
// instruction forms.
type Function struct {
	Instruction inst.Instruction
	Suffixes    inst.Suffixes
	inst.Forms
}

// Name returns the function name.
func (f *Function) Name() string {
	return f.opcodesuffix("_")
}

// Opcode returns the full Go opcode of the instruction built by this function. Includes any suffixes.
func (f *Function) Opcode() string {
	return f.opcodesuffix(".")
}

func (f *Function) opcodesuffix(sep string) string {
	n := f.Instruction.Opcode
	for _, suffix := range f.Suffixes {
		n += sep
		n += suffix.String()
	}
	return n
}

// HasSuffix reports whether the function has the provided suffix.
func (f *Function) HasSuffix(suffix inst.Suffix) bool {
	for _, s := range f.Suffixes {
		if s == suffix {
			return true
		}
	}
	return false
}

// Summary returns a summary of the instruction this function constructs.
func (f *Function) Summary() string {
	summary := f.Instruction.Summary
	if len(f.Suffixes) > 0 {
		summary += " (" + strings.Join(f.Suffixes.Summaries(), ", ") + ")"
	}
	return summary
}

// Doc returns the function document comment as a list of lines.
func (f *Function) Doc() []string {
	lines := []string{
		fmt.Sprintf("%s: %s.", f.Name(), f.Summary()),
		"",
		"Forms:",
		"",
	}

	// Write a table of instruction forms.
	buf := bytes.NewBuffer(nil)
	w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
	for _, form := range f.Forms {
		row := f.Opcode() + "\t" + strings.Join(form.Signature(), "\t") + "\n"
		fmt.Fprint(w, row)
	}
	w.Flush()

	tbl := strings.TrimSpace(buf.String())
	for _, line := range strings.Split(tbl, "\n") {
		lines = append(lines, "\t"+line)
	}

	return lines
}

// Signature of the function. Derived from the instruction forms generated by this function.
func (f *Function) Signature() Signature {
	// Handle the case of forms with multiple arities.
	switch {
	case f.IsVariadic():
		return variadic{name: "ops"}
	case f.IsNiladic():
		return niladic{}
	}

	// Generate nice-looking variable names.
	n := f.Arity()
	ops := make([]string, n)
	count := map[string]int{}
	for j := range n {
		// Collect unique lowercase bytes from first characters of operand types.
		s := map[byte]bool{}
		for _, form := range f.Forms {
			c := form.Operands[j].Type[0]
			if 'a' <= c && c <= 'z' {
				s[c] = true
			}
		}

		// Operand name is the sorted bytes.
		var b []byte
		for c := range s {
			b = append(b, c)
		}
		sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
		name := string(b)

		// Append a counter if we've seen it already.
		m := count[name]
		count[name]++
		if m > 0 {
			name += strconv.Itoa(m)
		}
		ops[j] = name
	}

	return argslist(ops)
}

// InstructionFunctions builds the list of all functions for a given
// instruction.
func InstructionFunctions(i inst.Instruction) []*Function {
	// One function for each possible suffix combination.
	bysuffix := map[string]*Function{}
	for _, f := range i.Forms {
		for _, suffixes := range f.SupportedSuffixes() {
			k := suffixes.String()
			if _, ok := bysuffix[k]; !ok {
				bysuffix[k] = &Function{
					Instruction: i,
					Suffixes:    suffixes,
				}
			}
			bysuffix[k].Forms = append(bysuffix[k].Forms, f)
		}
	}

	// Convert to a sorted slice.
	var fns []*Function
	for _, fn := range bysuffix {
		fns = append(fns, fn)
	}

	SortFunctions(fns)

	return fns
}

// InstructionsFunctions builds all functions for a list of instructions.
func InstructionsFunctions(is []inst.Instruction) []*Function {
	var all []*Function
	for _, i := range is {
		fns := InstructionFunctions(i)
		all = append(all, fns...)
	}

	SortFunctions(all)

	return all
}

// SortFunctions sorts a list of functions by name.
func SortFunctions(fns []*Function) {
	sort.Slice(fns, func(i, j int) bool {
		return fns[i].Name() < fns[j].Name()
	})
}
